package org.yoshiori.hatena.auth;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;


import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.yoshiori.commons.security.MessageDigest;
import org.yoshiori.commons.security.MessageDigest.DigestAlgorithm;
import org.yoshiori.hatena.HatenaException;

/**
 * <p>はてな認証を行うクラスです.<br/>
 * はてな認証の詳細については「<a href="http://auth.hatena.ne.jp/">はてな認証API</a>」こちらをご覧ください。</p>
 * 
 * <p>主な使い方としては<br/>
 * <pre><code> HatenaAuth auth = new HatenaAuth("api_key", "secret");
 * String link = auth.getAuthURL();</code></pre><br/>
 * で、認証用のURLが取得できます。<br/>
 * cert取得後、<br/>
 * <pre><code>User user =  auth.login(cert);</code></pre><br/>
 * でユーザー情報を取得できます。</p>
 * 
 * @author yoshiori
 * @see　<a href="http://auth.hatena.ne.jp/">はてな認証API</a>
 */
public class HatenaAuth {

    private String apiKey;

    private String secret;

    private MessageDigest md5;

    private String encode = "UTF-8";

    private static final String AUTH_URL = "http://auth.hatena.ne.jp/auth?";

    private static final String LOGIN_URL = "http://auth.hatena.ne.jp/api/auth.xml?";

    /**
     * @param apiKey はてなから取得したAPIキー
     * @param secret はてなから取得した秘密鍵
     */
    public HatenaAuth(String apiKey, String secret) {
        if(StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(secret) ){
            throw new NullPointerException("apikey or secret is null.");
        }
        this.apiKey = apiKey;
        this.secret = secret;
        md5 = new MessageDigest(DigestAlgorithm.MD5);
    }

    /**
     * apiKeyとsecretを元に
     * 認証用のURLが取得できます。
     * @return 認証用URL
     */
    public String getAuthURL() {
        return getAuthURL(null);
    }

    /**
     * apiKeyとsecretとparameterMapを元に
     * 認証用のURLが取得できます。<br/>
     * parameterMapについては<br/>
     * <a href="http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletRequest.html#getParameterMap()"><code>javax.servlet.ServletRequest.getParameterMap()</code></a>に<br/>
     * 準じています。
     * @param parameterMap 上記参照
     * @return 認証用URL
     * @see <a href="http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletRequest.html#getParameterMap()"><code>javax.servlet.ServletRequest.getParameterMap()</code></a>
     */
    public String getAuthURL(Map<String, String[]> parameterMap) {
        Map<String, String[]> params = new TreeMap<String, String[]>();
        if(parameterMap != null){
            params.putAll(parameterMap);
        }
        params.put("api_key", new String[] { apiKey });
        StringBuilder sigBuilder = new StringBuilder(secret);
        StringBuilder paramBuilder = new StringBuilder();
        for (String key : params.keySet()) {
            String[] parameterValues = params.get(key);
            Arrays.sort(parameterValues);
            for (String value : parameterValues) {
                sigBuilder.append(key).append(value);
                try {
                    paramBuilder.append("&")
                                .append(key)
                                .append("=")
                                .append(URLEncoder.encode(value, encode));
                } catch (UnsupportedEncodingException e) {
                    throw new HatenaException(e);
                }
            }
        }
        String sig = md5.getHexString(sigBuilder.toString());
        return AUTH_URL + "api_sig=" + sig + paramBuilder.toString();
    }

    /**
     * certを元にユーザーデータを取得します。
     * @param cert 認証ハンドラから取得したcert の値
     * @return {@link User} はてなユーザーデータ
     */
    public User login(String cert){
        if (cert == null) {
            throw new NullPointerException("cert is null");
        }
        StringBuilder sigBuilder = new StringBuilder(secret);
        sigBuilder.append("api_key").append(apiKey);
        sigBuilder.append("cert").append(cert);
        String sig = md5.getHexString(sigBuilder.toString());
        URL url = null;
        try {
            url = new URL(LOGIN_URL + "api_key=" + apiKey + "&cert=" + cert + "&api_sig=" + sig);
        } catch (MalformedURLException e) {
            throw new HatenaException(e);
        }
        SAXReader xmlReader = new SAXReader();
        try {
            return getUser(xmlReader.read(url));
        } catch (DocumentException e) {
            throw new HatenaException(e);
        }
    }

    /**
     * @param document
     * @return
     * @throws LoginException
     */
    protected User getUser(Document document) {
        User user = new User();
        Element root = document.getRootElement();
        if ("true".equals(root.elementText("has_error"))) {
            throw new HatenaException(root.element("error").elementText("message"));
        }
        Element userElement = root.element("user");
        user.setName(userElement.elementText("name"));
        user.setImageURL(userElement.elementText("image_url"));
        user.setThumbnailURL(userElement.elementText("thumbnail_url"));
        return user;
    }

    /**
     * @return
     */
    public String getEncode() {
        return encode;
    }

    /**
     * @param encode パラメータの文字コード(デフォルトはUTF-8)
     */
    public void setEncode(String encode) {
        this.encode = encode;
        this.md5.setEncoding(encode);
    }
}
